package com.wnxy.sddfp.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sddfp.common.result.Result;
import com.wnxy.sddfp.common.result.enums.IErrorCode;
import com.wnxy.sddfp.common.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.utils.JwtTemplate;
import com.wnxy.sddfp.gateway.config.IgnorePathConfig;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

/**
 * 网关全局过滤器，实现统一鉴权：拦截所有的请求
 *
 * GlobalFilter   全局过滤器
 * 自己需要使用拦截器的时候:需要 实现 GlobalFilter 这个接口:
 *     filter 完成业务逻辑的方法
 *
 */
@Configuration
public class AuthFilter implements GlobalFilter {

    //springIOC
    //依赖注入:
    //       构造器注入
    //       @AutoWorid
    public final JwtTemplate jwtTemplate;
    public final StringRedisTemplate stringRedisTemplate;
    public final IgnorePathConfig ignorePathConfig;
    public AuthFilter(JwtTemplate jwtTemplate, StringRedisTemplate stringRedisTemplate, IgnorePathConfig ignorePathConfig) {
        this.jwtTemplate = jwtTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
        this.ignorePathConfig = ignorePathConfig;
    }

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";

    /**
     *  gateway 微服务统一鉴权
     *  前端   :
     *       1、用户名或者手机号码登录
     *       2、前端校验数据
     *       3、校验密码 或者 从 redis 中 获取 验证码
     *       4、登录成功
     *              手机验证码登录、如果是第一次登录 自动注册 用户
     *       5、返回结果
     *             UserVo  token、生成token的步骤:
     *                 使用jwt 存储 userId 生产一个字符串(存储在redis) 就是token 返回给前端
     *    前端需要认证的访问接口的时候:
     *         需要携带token过去
     *              头信息中需要设置 ：
     *              "Authorization":"Bearer token"
     *  网关中的操作：
     *     1、拿到 请求的URL
     *     2、判断 这个url是否需要校验
     *     3、拿到请求的头信息
     *     4、拿到请求的token
     *     5、校验token 是否合法
     *     6、token 过期 (从redis 获取是否过期)
     *     7、续签 (防止过期)
     *     8、请求头重写 (其他的微服务 可以直接获取userId)
     *
     */
    //webFlux   spring 的异步请求
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1、获取请求路径：放行一些资源
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String path = request.getURI().getPath();
        System.out.println("path = " + path);
        AntPathMatcher pathMatcher = new AntPathMatcher();
        List<String> whiteList = ignorePathConfig.getWhiteList();

        boolean flag = whiteList.stream().anyMatch(urlPatter -> pathMatcher.match(urlPatter,path));
        if (flag) {
            // 放行，不进行token校验
            return chain.filter(exchange);
        }

        //2、需要先获取请求头，去掉token前缀“Bearer ”， 得到token
        String authToken = request.getHeaders().getFirst(AUTHORIZATION);
        if (StringUtils.isEmpty(authToken)) {
            return responseError(response,BusinessCode.TOKEN_IS_NULL);
        }
        authToken = authToken.replace(BEARER,"");

        //3、校验token
        if (!jwtTemplate.verify(authToken)) {
            return responseError(response,BusinessCode.TOKEN_IS_INVALID);
        }

        //4、解析获取token数据： userId, 校验token是否过期
        Object userId =  jwtTemplate.parseToken(authToken, "userId");
        String key = "TOKEN:" + userId.toString();
        if (!stringRedisTemplate.hasKey(key)) {
            return responseError(response,BusinessCode.TOKEN_IS_EXPIRED);
        }

        //5、token 续期
        stringRedisTemplate.expire(key, Duration.ofDays(30L));

        //6、网关中重写请求头，让请求头携带userId，再转发到微服务，这样在微服务中就可以直接获取登录用户id
        request.mutate().header("userId",userId.toString());

        //7、微服务直接从请求头获取用户id
        return chain.filter(exchange);
    }

    private static Mono<Void> responseError(ServerHttpResponse response, IErrorCode resultCode) throws JsonProcessingException {
        // 返回错误
        Result<Object> result = Result.fail(resultCode);
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(dataBuffer));
    }

}